Skip to content

feat(runtime): add RuntimeTracer trait for task instrumentation#2467

Open
xanderbailey wants to merge 3 commits into
apache:mainfrom
xanderbailey:feat/runtime-tracer
Open

feat(runtime): add RuntimeTracer trait for task instrumentation#2467
xanderbailey wants to merge 3 commits into
apache:mainfrom
xanderbailey:feat/runtime-tracer

Conversation

@xanderbailey
Copy link
Copy Markdown
Contributor

@xanderbailey xanderbailey commented May 19, 2026

Allows callers to inject observability (tracing spans, metrics, etc.) into spawned tasks without modifying spawn sites. Inspired by DataFusion's JoinSetTracer but scoped per-Runtime rather than global, enabling different tracers for IO vs CPU handles.

Why RuntimeTracer instead of tokio's native tracing?

iceberg-rust is a library, it shouldn't dictate the observability stack. RuntimeTracer provides a spawn-site hook that lets consumers inject their own instrumentation without coupling the library to any specific tracing crate.

Concern tokio tracing RuntimeTracer
Dependency Requires tracing crate in the library Zero added dependencies
Stack choice Tied to tracing ecosystem (subscribers, layers) Stack-neutral — implement with tracing, OTel, Prometheus, log, or anything else
Split runtimes No concept of IO vs CPU categorization Hooks are attached per-RuntimeHandle, so IO and CPU work are distinguishable
Control Library authors decide what to instrument Library consumers decide what to instrument

The two approaches are complementary: a consumer could implement RuntimeTracer to attach a tracing::Span, getting tokio-console visibility and library-level spawn-site context without iceberg taking an opinion.

Which issue does this PR close?

What changes are included in this PR?

Are these changes tested?

Allows callers to inject observability (tracing spans, metrics, etc.)
into spawned tasks without modifying spawn sites. Inspired by
DataFusion's JoinSetTracer but scoped per-Runtime rather than global,
enabling different tracers for IO vs CPU handles.
@xanderbailey
Copy link
Copy Markdown
Contributor Author

@CTTY would be interested on your thoughts here?

@CTTY
Copy link
Copy Markdown
Collaborator

CTTY commented May 21, 2026

Hi Xander,

Thanks for having this!
I also thought about this before and was thinking that the tracing part could be deferred to the custom implementation once we made Runtime itself a trait. But I don't have a strong opinion here.

Curious to hear what others think as well

@xanderbailey
Copy link
Copy Markdown
Contributor Author

Makes sense, I thought about this too and this trait is designed to be agnostic of the concrete runtime (tokio) in the hope that it wouldn’t need to be changed. It just generally wraps futures.

Is the worry that we don’t know if that’s actually true until we design a runtime trait?

Comment on lines +168 to +169
self.io.tracer = Some(tracer.clone());
self.cpu.tracer = Some(tracer);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the io.tracer cloned, but the cpu tracer isn't?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need it twice so the first is a "move" but clone of Arc is very cheap

/// The implementation must not alter the closure's return value.
fn trace_block(
&self,
f: Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is blocking do we really need the Send trait?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tokio spawn_blocking requires Send because you pass these to thread pool so you do cross a thread boundary. https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html

) -> impl FnOnce() -> T + Send + 'static
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above why do we need Send when this is blocking?

@blackmwk
Copy link
Copy Markdown
Contributor

Thanks @xanderbailey for this pr, but I'm not convinced that this is the right direction to go. The motivation you mentioned have two parts:

  1. Not relying on tracing for observability. This is only a benefit in theory, in practice what user are mostly concerned about are collecting the spans with different sink rather than the library used to generate the spans. tracing is widely used and has a large ecosystem, what's more we are already relying on tracing for logging and method tracking.
  2. Library consumers decide what to instrument. This is a good point, but I don't know what kinds of information library consumer could get from a BoxFuture. In fact, tokio runtime has provided some instrument point: see on_after_task_poll, on_before_task_poll, etc. I think the context provided by tokio's instrument point is more useful than a BoxFuture, and that's the right direction to go since iceberg crate's Runtime is built one tokio Runtime, which is created and maintained by user.

@xanderbailey
Copy link
Copy Markdown
Contributor Author

xanderbailey commented May 29, 2026

The strongest example is span propagation across spawn boundaries. When you tokio::spawn, the tracing span context is severed, the spawned task doesn't inherit the parent span. So if you're tracing a table scan that spawns 50 IO tasks to fetch manifests and data files, those tasks appear as orphans in your Jaeger/Tempo traces rather than children of the scan.

Very similar conversation was had when this was introduced in datafusion apache/datafusion#14547

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tracing to the new runtime

4 participants